/*-------------------------------------------------------------------------
 *
 * jsonpath.c
 *	 jsonpath 的输入/输出和支持例程
 *
 * jsonpath 表达式是一个路径项的链。第一个路径项是 $、$var、字面量或算术表达式。后续路径项是访问器（.key、.*、[下标]、[*]）、过滤器（?(谓词））和方法（.type()、.size() 等）。
 *
 * 例如，简单表达式的路径项结构：
 *
 *		$.a[*].type()
 *
 * 是相当明显的：
 *
 *		$ => .a => [*] => .type()
 *
 * 有些路径项，如算术运算、谓词或数组下标，可能包含子树。例如，更复杂的表达式：
 *
 *		($.a + $[1 to 5, 7] ? (@ > 3).double()).type()
 *
 * 具有以下路径项结构：
 *
 *			  +  =>  .type()
 *		  ___/ \___
 *		 /		   \
 *		$ => .a 	$  =>  []  =>	?  =>  .double()
 *						  _||_		|
 *						 /	  \ 	>
 *						to	  to   / \
 *					   / \	  /   @   3
 *					  1   5  7
 *
 * jsonpath 的二进制编码构成一系列 4 字节对齐的可变长度路径项，通过链接相连。每个项都有一个包含项类型（enum JsonPathItemType）和下一个项的偏移量的头（零表示没有下一个项）。在头之后，项可能具有有效载荷，具体取决于项类型。例如，'.key' 访问器项的有效载荷是键名的长度和键名本身。'>' 算术运算符项的有效载荷是右操作数和左操作数的偏移量。
 *
 * 因此，上述示例表达式的二进制表示为：
 * （底部箭头是下一个链接，上面的线是参数链接）
 *
 *								  _____
 *		 _____				  ___/____ \				__
 *	  _ /_	  \ 		_____/__/____ \ \	   __    _ /_ \
 *	 / /  \    \	   /	/  /	 \ \ \ 	  /  \  / /  \ \
 * +(LR)  $ .a	$  [](* to *, * to *) 1 5 7 ?(A)  >(LR)   @ 3 .double() .type()
 * |	  |  ^	|  ^|						 ^|					  ^		   ^
 * |	  |__|	|__||________________________||___________________|		   |
 * |_______________________________________________________________________|
 *
 * Copyright (c) 2019-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	src/backend/utils/adt/jsonpath.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "funcapi.h"
#include "lib/stringinfo.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/json.h"
#include "utils/jsonpath.h"


static Datum fc_jsonPathFromCstring(char *fc_in, int fc_len);
static char *fc_jsonPathToCstring(StringInfo fc_out, JsonPath *fc_in,
							   int fc_estimated_len);
static int	fc_flattenJsonPathParseItem(StringInfo fc_buf, JsonPathParseItem *fc_item,
									 int fc_nestingLevel, bool fc_insideArraySubscript);
static void fc_alignStringInfoInt(StringInfo fc_buf);
static int32 fc_reserveSpaceForItemPointer(StringInfo fc_buf);
static void fc_printJsonPathItem(StringInfo fc_buf, JsonPathItem *fc_v, bool fc_inKey,
							  bool fc_printBracketes);
static int	fc_operationPriority(JsonPathItemType fc_op);


/**************************** 输入/输出 ********************************/

/*
 * jsonpath 类型输入函数
 */
Datum jsonpath_in(PG_FUNCTION_ARGS)
{
	char	   *fc_in = PG_GETARG_CSTRING(0);
	int			fc_len = strlen(fc_in);

	return fc_jsonPathFromCstring(fc_in, fc_len);
}

/*
 * jsonpath 类型接收函数
 *
 * 类型以文本形式在二进制模式下发送，因此这几乎与
 * 输入函数相同，但它前面带有版本号，以便我们
 * 在未来必要时可以更改发送的二进制格式。目前，
 * 仅支持版本 1。
 */
Datum jsonpath_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);
	int			fc_version = pq_getmsgint(fc_buf, 1);
	char	   *fc_str;
	int			fc_nbytes;

	if (fc_version == JSONPATH_VERSION)
		fc_str = pq_getmsgtext(fc_buf, fc_buf->len - fc_buf->cursor, &fc_nbytes);
	else
		elog(ERROR, "unsupported jsonpath version number: %d", fc_version);

	return fc_jsonPathFromCstring(fc_str, fc_nbytes);
}

/*
 * jsonpath 类型输出函数
 */
Datum jsonpath_out(PG_FUNCTION_ARGS)
{
	JsonPath   *fc_in = PG_GETARG_JSONPATH_P(0);

	PG_RETURN_CSTRING(fc_jsonPathToCstring(NULL, fc_in, VARSIZE(fc_in)));
}

/*
 * jsonpath 类型发送函数
 *
 * 只需将 jsonpath 作为版本号发送，然后发送字符串文本
 */
Datum jsonpath_send(PG_FUNCTION_ARGS)
{
	JsonPath   *fc_in = PG_GETARG_JSONPATH_P(0);
	StringInfoData fc_buf;
	StringInfoData fc_jtext;
	int			fc_version = JSONPATH_VERSION;

	initStringInfo(&fc_jtext);
	(void) fc_jsonPathToCstring(&fc_jtext, fc_in, VARSIZE(fc_in));

	pq_begintypsend(&fc_buf);
	pq_sendint8(&fc_buf, fc_version);
	pq_sendtext(&fc_buf, fc_jtext.data, fc_jtext.len);
	pfree(fc_jtext.data);

	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}

/*
 * 将 C 字符串转换为 jsonpath 值。
 *
 * 使用 jsonpath 解析器将字符串转换为 AST，然后
 * flattenJsonPathParseItem() 进行第二次遍历，将 AST 转换为
 * jsonpath 的二进制表示。
 */
static Datum fc_jsonPathFromCstring(char *fc_in, int fc_len)
{
	JsonPathParseResult *fc_jsonpath = parsejsonpath(fc_in, fc_len);
	JsonPath   *fc_res;
	StringInfoData fc_buf;

	initStringInfo(&fc_buf);
	enlargeStringInfo(&fc_buf, 4 * fc_len /* 估算 */ );

	appendStringInfoSpaces(&fc_buf, JSONPATH_HDRSZ);

	if (!fc_jsonpath)
#ifdef FDD//cppcheck
	{
#endif
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
						fc_in)));
#ifdef FDD//cppcheck
		PG_RETURN_VOID();
	}
#endif
	fc_flattenJsonPathParseItem(&fc_buf, fc_jsonpath->expr, 0, false);

	fc_res = (JsonPath *) fc_buf.data;
	SET_VARSIZE(fc_res, fc_buf.len);
	fc_res->header = JSONPATH_VERSION;
	if (fc_jsonpath->lax)
		fc_res->header |= JSONPATH_LAX;

	PG_RETURN_JSONPATH_P(fc_res);
}

/*
 * 将 jsonpath 值转换为 C 字符串。
 *
 * 如果 'out' 参数不为 null，则结果 C 字符串存储在
 * StringBuffer 中。返回的字符串始终有效。
 */
static char * fc_jsonPathToCstring(StringInfo fc_out, JsonPath *fc_in, int fc_estimated_len)
{
	StringInfoData fc_buf;
	JsonPathItem fc_v;

	if (!fc_out)
	{
		fc_out = &fc_buf;
		initStringInfo(fc_out);
	}
	enlargeStringInfo(fc_out, fc_estimated_len);

	if (!(fc_in->header & JSONPATH_LAX))
		appendBinaryStringInfo(fc_out, "strict ", 7);

	jspInit(&fc_v, fc_in);
	fc_printJsonPathItem(fc_out, &fc_v, false, true);

	return fc_out->data;
}

/*
 * 递归函数，将给定的 jsonpath 解析项及其所有
 * 子项转换为二进制表示。
 */
static int fc_flattenJsonPathParseItem(StringInfo fc_buf, JsonPathParseItem *fc_item,
						 int fc_nestingLevel, bool fc_insideArraySubscript)
{
	/* 从 jsonpath 数据的开头开始的位置 */
	int32		fc_pos = fc_buf->len - JSONPATH_HDRSZ;
	int32		fc_chld;
	int32		fc_next;
	int			fc_argNestingLevel = 0;

	check_stack_depth();
	CHECK_FOR_INTERRUPTS();

	appendStringInfoChar(fc_buf, (char) (fc_item->type));

	/*
	 * 我们将缓冲区对齐到 int32，因为一系列 int32 值通常在
	 * 头部之后，并且我们希望通过解引用
	 * int32 指针直接读取它们（参见 jspInitByBuffer()）。
	 */
	fc_alignStringInfoInt(fc_buf);

	/*
	 * 为下一个项指针保留空间。实际值将在
	 * 处理下一个和子项后记录。
	 */
	fc_next = fc_reserveSpaceForItemPointer(fc_buf);

	switch (fc_item->type)
	{
		case jpiString:
		case jpiVariable:
		case jpiKey:
			appendBinaryStringInfo(fc_buf, (char *) &fc_item->value.string.len,
								   sizeof(fc_item->value.string.len));
			appendBinaryStringInfo(fc_buf, fc_item->value.string.val,
								   fc_item->value.string.len);
			appendStringInfoChar(fc_buf, '\0');
			break;
		case jpiNumeric:
			appendBinaryStringInfo(fc_buf, (char *) fc_item->value.numeric,
								   VARSIZE(fc_item->value.numeric));
			break;
		case jpiBool:
			appendBinaryStringInfo(fc_buf, (char *) &fc_item->value.boolean,
								   sizeof(fc_item->value.boolean));
			break;
		case jpiAnd:
		case jpiOr:
		case jpiEqual:
		case jpiNotEqual:
		case jpiLess:
		case jpiGreater:
		case jpiLessOrEqual:
		case jpiGreaterOrEqual:
		case jpiAdd:
		case jpiSub:
		case jpiMul:
		case jpiDiv:
		case jpiMod:
		case jpiStartsWith:
			{
				/*
				 * 首先，为左/右参数的位置保留位置，然后
				 * 记录两个参数，并在保留的位置
				 * 中设置实际位置。
				 */
				int32		fc_left = fc_reserveSpaceForItemPointer(fc_buf);
				int32		fc_right = fc_reserveSpaceForItemPointer(fc_buf);

				fc_chld = !fc_item->value.args.left ? fc_pos :
					fc_flattenJsonPathParseItem(fc_buf, fc_item->value.args.left,
											 fc_nestingLevel + fc_argNestingLevel,
											 fc_insideArraySubscript);
				*(int32 *) (fc_buf->data + fc_left) = fc_chld - fc_pos;

				fc_chld = !fc_item->value.args.right ? fc_pos :
					fc_flattenJsonPathParseItem(fc_buf, fc_item->value.args.right,
											 fc_nestingLevel + fc_argNestingLevel,
											 fc_insideArraySubscript);
				*(int32 *) (fc_buf->data + fc_right) = fc_chld - fc_pos;
			}
			break;
		case jpiLikeRegex:
			{
				int32		fc_offs;

				appendBinaryStringInfo(fc_buf,
									   (char *) &fc_item->value.like_regex.flags,
									   sizeof(fc_item->value.like_regex.flags));
				fc_offs = fc_reserveSpaceForItemPointer(fc_buf);
				appendBinaryStringInfo(fc_buf,
									   (char *) &fc_item->value.like_regex.patternlen,
									   sizeof(fc_item->value.like_regex.patternlen));
				appendBinaryStringInfo(fc_buf, fc_item->value.like_regex.pattern,
									   fc_item->value.like_regex.patternlen);
				appendStringInfoChar(fc_buf, '\0');

				fc_chld = fc_flattenJsonPathParseItem(fc_buf, fc_item->value.like_regex.expr,
												fc_nestingLevel,
												fc_insideArraySubscript);
				*(int32 *) (fc_buf->data + fc_offs) = fc_chld - fc_pos;
			}
			break;
		case jpiFilter:
			fc_argNestingLevel++;
			/* FALLTHROUGH */
		case jpiIsUnknown:
		case jpiNot:
		case jpiPlus:
		case jpiMinus:
		case jpiExists:
		case jpiDatetime:
			{
				int32		fc_arg = fc_reserveSpaceForItemPointer(fc_buf);

				fc_chld = !fc_item->value.arg ? fc_pos :
					fc_flattenJsonPathParseItem(fc_buf, fc_item->value.arg,
											 fc_nestingLevel + fc_argNestingLevel,
											 fc_insideArraySubscript);
				*(int32 *) (fc_buf->data + fc_arg) = fc_chld - fc_pos;
			}
			break;
		case jpiNull:
			break;
		case jpiRoot:
			break;
		case jpiAnyArray:
		case jpiAnyKey:
			break;
		case jpiCurrent:
			if (fc_nestingLevel <= 0)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("@ is not allowed in root expressions")));
			break;
		case jpiLast:
			if (!fc_insideArraySubscript)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("LAST is allowed only in array subscripts")));
			break;
		case jpiIndexArray:
			{
				int32		fc_nelems = fc_item->value.array.nelems;
				int			fc_offset;
				int			fc_i;

				appendBinaryStringInfo(fc_buf, (char *) &fc_nelems, sizeof(fc_nelems));

				fc_offset = fc_buf->len;

				appendStringInfoSpaces(fc_buf, sizeof(int32) * 2 * fc_nelems);

				for (fc_i = 0; fc_i < fc_nelems; fc_i++)
				{
					int32	   *fc_ppos;
					int32		fc_topos;
					int32		fc_frompos =
					fc_flattenJsonPathParseItem(fc_buf,
											 fc_item->value.array.elems[fc_i].from,
											 fc_nestingLevel, true) - fc_pos;

					if (fc_item->value.array.elems[fc_i].to)
						fc_topos = fc_flattenJsonPathParseItem(fc_buf,
														 fc_item->value.array.elems[fc_i].to,
														 fc_nestingLevel, true) - fc_pos;
					else
						fc_topos = 0;

					fc_ppos = (int32 *) &fc_buf->data[fc_offset + fc_i * 2 * sizeof(int32)];

					fc_ppos[0] = fc_frompos;
					fc_ppos[1] = fc_topos;
				}
			}
			break;
		case jpiAny:
			appendBinaryStringInfo(fc_buf,
								   (char *) &fc_item->value.anybounds.first,
								   sizeof(fc_item->value.anybounds.first));
			appendBinaryStringInfo(fc_buf,
								   (char *) &fc_item->value.anybounds.last,
								   sizeof(fc_item->value.anybounds.last));
			break;
		case jpiType:
		case jpiSize:
		case jpiAbs:
		case jpiFloor:
		case jpiCeiling:
		case jpiDouble:
		case jpiKeyValue:
			break;
		default:
			elog(ERROR, "unrecognized jsonpath item type: %d", fc_item->type);
	}

	if (fc_item->next)
	{
		fc_chld = fc_flattenJsonPathParseItem(fc_buf, fc_item->next, fc_nestingLevel,
										fc_insideArraySubscript) - fc_pos;
		*(int32 *) (fc_buf->data + fc_next) = fc_chld;
	}

	return fc_pos;
}

/*
 * 通过添加零填充字节将 StringInfo 对齐到整数
 */
static void fc_alignStringInfoInt(StringInfo fc_buf)
{
	switch (INTALIGN(fc_buf->len) - fc_buf->len)
	{
		case 3:
			appendStringInfoCharMacro(fc_buf, 0);
			/* FALLTHROUGH */
		case 2:
			appendStringInfoCharMacro(fc_buf, 0);
			/* FALLTHROUGH */
		case 1:
			appendStringInfoCharMacro(fc_buf, 0);
			/* FALLTHROUGH */
		default:
			break;
	}
}

/*
 * 为 int32 JsonPathItem 指针保留空间。现在写入零指针，
 * 实际值将在 '(int32 *) &buf->data[pos]' 处稍后记录。
 */
static int32 fc_reserveSpaceForItemPointer(StringInfo fc_buf)
{
	int32		fc_pos = fc_buf->len;
	int32		fc_ptr = 0;

	appendBinaryStringInfo(fc_buf, (char *) &fc_ptr, sizeof(fc_ptr));

	return fc_pos;
}

/*
 * 打印给定 jsonpath 项及其所有子项的文本表示。
 */
static void fc_printJsonPathItem(StringInfo fc_buf, JsonPathItem *fc_v, bool fc_inKey,
				  bool fc_printBracketes)
{
	JsonPathItem fc_elem;
	int			fc_i;

	check_stack_depth();
	CHECK_FOR_INTERRUPTS();

	switch (fc_v->type)
	{
		case jpiNull:
			appendStringInfoString(fc_buf, "null");
			break;
		case jpiKey:
			if (fc_inKey)
				appendStringInfoChar(fc_buf, '.');
			escape_json(fc_buf, jspGetString(fc_v, NULL));
			break;
		case jpiString:
			escape_json(fc_buf, jspGetString(fc_v, NULL));
			break;
		case jpiVariable:
			appendStringInfoChar(fc_buf, '$');
			escape_json(fc_buf, jspGetString(fc_v, NULL));
			break;
		case jpiNumeric:
			if (jspHasNext(fc_v))
				appendStringInfoChar(fc_buf, '(');
			appendStringInfoString(fc_buf,
								   DatumGetCString(DirectFunctionCall1(numeric_out,
																	   NumericGetDatum(jspGetNumeric(fc_v)))));
			if (jspHasNext(fc_v))
				appendStringInfoChar(fc_buf, ')');
			break;
		case jpiBool:
			if (jspGetBool(fc_v))
				appendBinaryStringInfo(fc_buf, "true", 4);
			else
				appendBinaryStringInfo(fc_buf, "false", 5);
			break;
		case jpiAnd:
		case jpiOr:
		case jpiEqual:
		case jpiNotEqual:
		case jpiLess:
		case jpiGreater:
		case jpiLessOrEqual:
		case jpiGreaterOrEqual:
		case jpiAdd:
		case jpiSub:
		case jpiMul:
		case jpiDiv:
		case jpiMod:
		case jpiStartsWith:
			if (fc_printBracketes)
				appendStringInfoChar(fc_buf, '(');
			jspGetLeftArg(fc_v, &fc_elem);
			fc_printJsonPathItem(fc_buf, &fc_elem, false,
							  fc_operationPriority(fc_elem.type) <=
							  fc_operationPriority(fc_v->type));
			appendStringInfoChar(fc_buf, ' ');
			appendStringInfoString(fc_buf, jspOperationName(fc_v->type));
			appendStringInfoChar(fc_buf, ' ');
			jspGetRightArg(fc_v, &fc_elem);
			fc_printJsonPathItem(fc_buf, &fc_elem, false,
							  fc_operationPriority(fc_elem.type) <=
							  fc_operationPriority(fc_v->type));
			if (fc_printBracketes)
				appendStringInfoChar(fc_buf, ')');
			break;
		case jpiLikeRegex:
			if (fc_printBracketes)
				appendStringInfoChar(fc_buf, '(');

			jspInitByBuffer(&fc_elem, fc_v->base, fc_v->content.like_regex.expr);
			fc_printJsonPathItem(fc_buf, &fc_elem, false,
							  fc_operationPriority(fc_elem.type) <=
							  fc_operationPriority(fc_v->type));

			appendBinaryStringInfo(fc_buf, " like_regex ", 12);

			escape_json(fc_buf, fc_v->content.like_regex.pattern);

			if (fc_v->content.like_regex.flags)
			{
				appendBinaryStringInfo(fc_buf, " flag \"", 7);

				if (fc_v->content.like_regex.flags & JSP_REGEX_ICASE)
					appendStringInfoChar(fc_buf, 'i');
				if (fc_v->content.like_regex.flags & JSP_REGEX_DOTALL)
					appendStringInfoChar(fc_buf, 's');
				if (fc_v->content.like_regex.flags & JSP_REGEX_MLINE)
					appendStringInfoChar(fc_buf, 'm');
				if (fc_v->content.like_regex.flags & JSP_REGEX_WSPACE)
					appendStringInfoChar(fc_buf, 'x');
				if (fc_v->content.like_regex.flags & JSP_REGEX_QUOTE)
					appendStringInfoChar(fc_buf, 'q');

				appendStringInfoChar(fc_buf, '"');
			}

			if (fc_printBracketes)
				appendStringInfoChar(fc_buf, ')');
			break;
		case jpiPlus:
		case jpiMinus:
			if (fc_printBracketes)
				appendStringInfoChar(fc_buf, '(');
			appendStringInfoChar(fc_buf, fc_v->type == jpiPlus ? '+' : '-');
			jspGetArg(fc_v, &fc_elem);
			fc_printJsonPathItem(fc_buf, &fc_elem, false,
							  fc_operationPriority(fc_elem.type) <=
							  fc_operationPriority(fc_v->type));
			if (fc_printBracketes)
				appendStringInfoChar(fc_buf, ')');
			break;
		case jpiFilter:
			appendBinaryStringInfo(fc_buf, "?(", 2);
			jspGetArg(fc_v, &fc_elem);
			fc_printJsonPathItem(fc_buf, &fc_elem, false, false);
			appendStringInfoChar(fc_buf, ')');
			break;
		case jpiNot:
			appendBinaryStringInfo(fc_buf, "!(", 2);
			jspGetArg(fc_v, &fc_elem);
			fc_printJsonPathItem(fc_buf, &fc_elem, false, false);
			appendStringInfoChar(fc_buf, ')');
			break;
		case jpiIsUnknown:
			appendStringInfoChar(fc_buf, '(');
			jspGetArg(fc_v, &fc_elem);
			fc_printJsonPathItem(fc_buf, &fc_elem, false, false);
			appendBinaryStringInfo(fc_buf, ") is unknown", 12);
			break;
		case jpiExists:
			appendBinaryStringInfo(fc_buf, "exists (", 8);
			jspGetArg(fc_v, &fc_elem);
			fc_printJsonPathItem(fc_buf, &fc_elem, false, false);
			appendStringInfoChar(fc_buf, ')');
			break;
		case jpiCurrent:
			Assert(!fc_inKey);
			appendStringInfoChar(fc_buf, '@');
			break;
		case jpiRoot:
			Assert(!fc_inKey);
			appendStringInfoChar(fc_buf, '$');
			break;
		case jpiLast:
			appendBinaryStringInfo(fc_buf, "last", 4);
			break;
		case jpiAnyArray:
			appendBinaryStringInfo(fc_buf, "[*]", 3);
			break;
		case jpiAnyKey:
			if (fc_inKey)
				appendStringInfoChar(fc_buf, '.');
			appendStringInfoChar(fc_buf, '*');
			break;
		case jpiIndexArray:
			appendStringInfoChar(fc_buf, '[');
			for (fc_i = 0; fc_i < fc_v->content.array.nelems; fc_i++)
			{
				JsonPathItem fc_from;
				JsonPathItem fc_to;
				bool		fc_range = jspGetArraySubscript(fc_v, &fc_from, &fc_to, fc_i);

				if (fc_i)
					appendStringInfoChar(fc_buf, ',');

				fc_printJsonPathItem(fc_buf, &fc_from, false, false);

				if (fc_range)
				{
					appendBinaryStringInfo(fc_buf, " to ", 4);
					fc_printJsonPathItem(fc_buf, &fc_to, false, false);
				}
			}
			appendStringInfoChar(fc_buf, ']');
			break;
		case jpiAny:
			if (fc_inKey)
				appendStringInfoChar(fc_buf, '.');

			if (fc_v->content.anybounds.first == 0 &&
				fc_v->content.anybounds.last == PG_UINT32_MAX)
				appendBinaryStringInfo(fc_buf, "**", 2);
			else if (fc_v->content.anybounds.first == fc_v->content.anybounds.last)
			{
				if (fc_v->content.anybounds.first == PG_UINT32_MAX)
					appendStringInfoString(fc_buf, "**{last}");
				else
					appendStringInfo(fc_buf, "**{%u}",
									 fc_v->content.anybounds.first);
			}
			else if (fc_v->content.anybounds.first == PG_UINT32_MAX)
				appendStringInfo(fc_buf, "**{last to %u}",
								 fc_v->content.anybounds.last);
			else if (fc_v->content.anybounds.last == PG_UINT32_MAX)
				appendStringInfo(fc_buf, "**{%u to last}",
								 fc_v->content.anybounds.first);
			else
				appendStringInfo(fc_buf, "**{%u to %u}",
								 fc_v->content.anybounds.first,
								 fc_v->content.anybounds.last);
			break;
		case jpiType:
			appendBinaryStringInfo(fc_buf, ".type()", 7);
			break;
		case jpiSize:
			appendBinaryStringInfo(fc_buf, ".size()", 7);
			break;
		case jpiAbs:
			appendBinaryStringInfo(fc_buf, ".abs()", 6);
			break;
		case jpiFloor:
			appendBinaryStringInfo(fc_buf, ".floor()", 8);
			break;
		case jpiCeiling:
			appendBinaryStringInfo(fc_buf, ".ceiling()", 10);
			break;
		case jpiDouble:
			appendBinaryStringInfo(fc_buf, ".double()", 9);
			break;
		case jpiDatetime:
			appendBinaryStringInfo(fc_buf, ".datetime(", 10);
			if (fc_v->content.arg)
			{
				jspGetArg(fc_v, &fc_elem);
				fc_printJsonPathItem(fc_buf, &fc_elem, false, false);
			}
			appendStringInfoChar(fc_buf, ')');
			break;
		case jpiKeyValue:
			appendBinaryStringInfo(fc_buf, ".keyvalue()", 11);
			break;
		default:
			elog(ERROR, "unrecognized jsonpath item type: %d", fc_v->type);
	}

	if (jspGetNext(fc_v, &fc_elem))
		fc_printJsonPathItem(fc_buf, &fc_elem, true, true);
}

const char * jspOperationName(JsonPathItemType fc_type)
{
	switch (fc_type)
	{
		case jpiAnd:
			return "&&";
		case jpiOr:
			return "||";
		case jpiEqual:
			return "==";
		case jpiNotEqual:
			return "!=";
		case jpiLess:
			return "<";
		case jpiGreater:
			return ">";
		case jpiLessOrEqual:
			return "<=";
		case jpiGreaterOrEqual:
			return ">=";
		case jpiPlus:
		case jpiAdd:
			return "+";
		case jpiMinus:
		case jpiSub:
			return "-";
		case jpiMul:
			return "*";
		case jpiDiv:
			return "/";
		case jpiMod:
			return "%";
		case jpiStartsWith:
			return "starts with";
		case jpiLikeRegex:
			return "like_regex";
		case jpiType:
			return "type";
		case jpiSize:
			return "size";
		case jpiKeyValue:
			return "keyvalue";
		case jpiDouble:
			return "double";
		case jpiAbs:
			return "abs";
		case jpiFloor:
			return "floor";
		case jpiCeiling:
			return "ceiling";
		case jpiDatetime:
			return "datetime";
		default:
			elog(ERROR, "unrecognized jsonpath item type: %d", fc_type);
			return NULL;
	}
}

static int fc_operationPriority(JsonPathItemType fc_op)
{
	switch (fc_op)
	{
		case jpiOr:
			return 0;
		case jpiAnd:
			return 1;
		case jpiEqual:
		case jpiNotEqual:
		case jpiLess:
		case jpiGreater:
		case jpiLessOrEqual:
		case jpiGreaterOrEqual:
		case jpiStartsWith:
			return 2;
		case jpiAdd:
		case jpiSub:
			return 3;
		case jpiMul:
		case jpiDiv:
		case jpiMod:
			return 4;
		case jpiPlus:
		case jpiMinus:
			return 5;
		default:
			return 6;
	}
}

/* ******************* 支持 JsonPath 的函数 ************************* */

/*
 * 用于读取存储值的支持宏
 */

#define read_byte(v, b, p) do {			\
	(v) = *(uint8*)((b) + (p));			\
	(p) += 1;							\
} while(0)								\

#define read_int32(v, b, p) do {		\
	(v) = *(uint32*)((b) + (p));		\
	(p) += sizeof(int32);				\
} while(0)								\

#define read_int32_n(v, b, p, n) do {	\
	(v) = (void *)((b) + (p));			\
	(p) += sizeof(int32) * (n);			\
} while(0)								\

/*
 * 读取根节点并填充根节点表示
 */
void jspInit(JsonPathItem *fc_v, JsonPath *fc_js)
{
	Assert((fc_js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
	jspInitByBuffer(fc_v, fc_js->data, 0);
}

/*
 * 从缓冲区读取节点并填充其表示
 */
void jspInitByBuffer(JsonPathItem *fc_v, char *fc_base, int32 fc_pos)
{
	fc_v->base = fc_base + fc_pos;

	read_byte(fc_v->type, fc_base, fc_pos);
	fc_pos = INTALIGN((uintptr_t) (fc_base + fc_pos)) - (uintptr_t) fc_base;
	read_int32(fc_v->nextPos, fc_base, fc_pos);

	switch (fc_v->type)
	{
		case jpiNull:
		case jpiRoot:
		case jpiCurrent:
		case jpiAnyArray:
		case jpiAnyKey:
		case jpiType:
		case jpiSize:
		case jpiAbs:
		case jpiFloor:
		case jpiCeiling:
		case jpiDouble:
		case jpiKeyValue:
		case jpiLast:
			break;
		case jpiKey:
		case jpiString:
		case jpiVariable:
			read_int32(fc_v->content.value.datalen, fc_base, fc_pos);
			/* FALLTHROUGH */
		case jpiNumeric:
		case jpiBool:
			fc_v->content.value.data = fc_base + fc_pos;
			break;
		case jpiAnd:
		case jpiOr:
		case jpiAdd:
		case jpiSub:
		case jpiMul:
		case jpiDiv:
		case jpiMod:
		case jpiEqual:
		case jpiNotEqual:
		case jpiLess:
		case jpiGreater:
		case jpiLessOrEqual:
		case jpiGreaterOrEqual:
		case jpiStartsWith:
			read_int32(fc_v->content.args.left, fc_base, fc_pos);
			read_int32(fc_v->content.args.right, fc_base, fc_pos);
			break;
		case jpiLikeRegex:
			read_int32(fc_v->content.like_regex.flags, fc_base, fc_pos);
			read_int32(fc_v->content.like_regex.expr, fc_base, fc_pos);
			read_int32(fc_v->content.like_regex.patternlen, fc_base, fc_pos);
			fc_v->content.like_regex.pattern = fc_base + fc_pos;
			break;
		case jpiNot:
		case jpiExists:
		case jpiIsUnknown:
		case jpiPlus:
		case jpiMinus:
		case jpiFilter:
		case jpiDatetime:
			read_int32(fc_v->content.arg, fc_base, fc_pos);
			break;
		case jpiIndexArray:
			read_int32(fc_v->content.array.nelems, fc_base, fc_pos);
			read_int32_n(fc_v->content.array.elems, fc_base, fc_pos,
						 fc_v->content.array.nelems * 2);
			break;
		case jpiAny:
			read_int32(fc_v->content.anybounds.first, fc_base, fc_pos);
			read_int32(fc_v->content.anybounds.last, fc_base, fc_pos);
			break;
		default:
			elog(ERROR, "unrecognized jsonpath item type: %d", fc_v->type);
	}
}

void jspGetArg(JsonPathItem *fc_v, JsonPathItem *fc_a)
{
	Assert(fc_v->type == jpiFilter ||
		   fc_v->type == jpiNot ||
		   fc_v->type == jpiIsUnknown ||
		   fc_v->type == jpiExists ||
		   fc_v->type == jpiPlus ||
		   fc_v->type == jpiMinus ||
		   fc_v->type == jpiDatetime);

	jspInitByBuffer(fc_a, fc_v->base, fc_v->content.arg);
}

bool jspGetNext(JsonPathItem *fc_v, JsonPathItem *fc_a)
{
	if (jspHasNext(fc_v))
	{
		Assert(fc_v->type == jpiString ||
			   fc_v->type == jpiNumeric ||
			   fc_v->type == jpiBool ||
			   fc_v->type == jpiNull ||
			   fc_v->type == jpiKey ||
			   fc_v->type == jpiAny ||
			   fc_v->type == jpiAnyArray ||
			   fc_v->type == jpiAnyKey ||
			   fc_v->type == jpiIndexArray ||
			   fc_v->type == jpiFilter ||
			   fc_v->type == jpiCurrent ||
			   fc_v->type == jpiExists ||
			   fc_v->type == jpiRoot ||
			   fc_v->type == jpiVariable ||
			   fc_v->type == jpiLast ||
			   fc_v->type == jpiAdd ||
			   fc_v->type == jpiSub ||
			   fc_v->type == jpiMul ||
			   fc_v->type == jpiDiv ||
			   fc_v->type == jpiMod ||
			   fc_v->type == jpiPlus ||
			   fc_v->type == jpiMinus ||
			   fc_v->type == jpiEqual ||
			   fc_v->type == jpiNotEqual ||
			   fc_v->type == jpiGreater ||
			   fc_v->type == jpiGreaterOrEqual ||
			   fc_v->type == jpiLess ||
			   fc_v->type == jpiLessOrEqual ||
			   fc_v->type == jpiAnd ||
			   fc_v->type == jpiOr ||
			   fc_v->type == jpiNot ||
			   fc_v->type == jpiIsUnknown ||
			   fc_v->type == jpiType ||
			   fc_v->type == jpiSize ||
			   fc_v->type == jpiAbs ||
			   fc_v->type == jpiFloor ||
			   fc_v->type == jpiCeiling ||
			   fc_v->type == jpiDouble ||
			   fc_v->type == jpiDatetime ||
			   fc_v->type == jpiKeyValue ||
			   fc_v->type == jpiStartsWith ||
			   fc_v->type == jpiLikeRegex);

		if (fc_a)
			jspInitByBuffer(fc_a, fc_v->base, fc_v->nextPos);
		return true;
	}

	return false;
}

void jspGetLeftArg(JsonPathItem *fc_v, JsonPathItem *fc_a)
{
	Assert(fc_v->type == jpiAnd ||
		   fc_v->type == jpiOr ||
		   fc_v->type == jpiEqual ||
		   fc_v->type == jpiNotEqual ||
		   fc_v->type == jpiLess ||
		   fc_v->type == jpiGreater ||
		   fc_v->type == jpiLessOrEqual ||
		   fc_v->type == jpiGreaterOrEqual ||
		   fc_v->type == jpiAdd ||
		   fc_v->type == jpiSub ||
		   fc_v->type == jpiMul ||
		   fc_v->type == jpiDiv ||
		   fc_v->type == jpiMod ||
		   fc_v->type == jpiStartsWith);

	jspInitByBuffer(fc_a, fc_v->base, fc_v->content.args.left);
}

void jspGetRightArg(JsonPathItem *fc_v, JsonPathItem *fc_a)
{
	Assert(fc_v->type == jpiAnd ||
		   fc_v->type == jpiOr ||
		   fc_v->type == jpiEqual ||
		   fc_v->type == jpiNotEqual ||
		   fc_v->type == jpiLess ||
		   fc_v->type == jpiGreater ||
		   fc_v->type == jpiLessOrEqual ||
		   fc_v->type == jpiGreaterOrEqual ||
		   fc_v->type == jpiAdd ||
		   fc_v->type == jpiSub ||
		   fc_v->type == jpiMul ||
		   fc_v->type == jpiDiv ||
		   fc_v->type == jpiMod ||
		   fc_v->type == jpiStartsWith);

	jspInitByBuffer(fc_a, fc_v->base, fc_v->content.args.right);
}

bool jspGetBool(JsonPathItem *fc_v)
{
	Assert(fc_v->type == jpiBool);

	return (bool) *fc_v->content.value.data;
}

Numeric jspGetNumeric(JsonPathItem *fc_v)
{
	Assert(fc_v->type == jpiNumeric);

	return (Numeric) fc_v->content.value.data;
}

char * jspGetString(JsonPathItem *fc_v, int32 *fc_len)
{
	Assert(fc_v->type == jpiKey ||
		   fc_v->type == jpiString ||
		   fc_v->type == jpiVariable);

	if (fc_len)
		*fc_len = fc_v->content.value.datalen;
	return fc_v->content.value.data;
}

bool jspGetArraySubscript(JsonPathItem *fc_v, JsonPathItem *fc_from, JsonPathItem *fc_to,
					 int fc_i)
{
	Assert(fc_v->type == jpiIndexArray);

	jspInitByBuffer(fc_from, fc_v->base, fc_v->content.array.elems[fc_i].from);

	if (!fc_v->content.array.elems[fc_i].to)
		return false;

	jspInitByBuffer(fc_to, fc_v->base, fc_v->content.array.elems[fc_i].to);

	return true;
}
